<template>
  <div class="pro-form">
    <n-form
      ref="formRef"
      :label-width="80"
      :model="formValue"
      v-bind="formProps"
    >
      <n-grid
        v-for="(item, index) in options"
        :key="index"
        :x-gap="(item.grid && item.grid['x-gap']) || 12"
        :y-gap="(item.grid && item.grid['y-gap']) || 8"
        :cols="(item.grid && item.grid.cols) || '1 s:2 m:4 l:4 xl:4 2xl:4'"
        :responsive="item.grid?.responsive ? item.grid?.responsive : 'screen'"
        v-bind="item.grid"
      >
        <template
          v-for="column in item.columnItemList"
          :key="column.formItem.key ? column.formItem.key : index"
        >
          <n-grid-item
            v-if="column.formItem.visiable !== false"
            v-bind="column.gridItem"
          >
            <n-form-item
              :label="column.formItem.label"
              :ref="column.formItem.key"
              :path="column.formItem.key"
              v-bind="column.formItem"
            >
              <!-- 前置插槽 -->
              <slot name="before" :column="column" :form="form" />
              <form-item
                v-if="column.formItem.formType !== 'custom'"
                :columnItem="column.formItem"
                v-model:model="formValue"
                v-bind="column.formItem"
                @formItemChange="handleChange"
              />
              <!-- render -->
              <component v-else :is="column.formItem.render" />
              <!-- 后置插槽 -->
              <slot name="after" :column="column" :form="form" />
            </n-form-item>
          </n-grid-item>
        </template>
      </n-grid>
    </n-form>
    <div
      v-if="showSubmitButton || showResetButton || buttonRender"
      class="buttons"
    >
      <n-button v-if="showSubmitButton" @click="handleSubmit" class="button">
        提交
      </n-button>
      <n-button v-if="showResetButton" @click="handleReset" class="button">
        重置
      </n-button>
      <!-- render按钮数组 -->
      <template v-for="button in buttonRender" :key="button.key">
        <component :is="button.render"></component>
      </template>
    </div>
  </div>
</template>

<script lang="ts" setup name="proForm">
  import { Ref } from 'vue'
  import { ProFormOptions, FormItemProps } from './type'
  import formItem from './components/form-item.vue'
  import { cloneDeep } from 'lodash-es'
  import { FormProps } from 'naive-ui'

  const { proxy }: any = getCurrentInstance()
  interface MyOptions {
    // form表单配置
    options: ProFormOptions
    // form对象
    form?: object
    // 整个form的配置
    formProps?: FormProps
    // 验证失败处理函数
    validateErrorFunc?: () => void
    // 验证成功处理函数
    validatePassFunc?: () => void
    // 是否显示提交按钮
    showSubmitButton?: boolean
    // 是否显示重置按钮
    showResetButton?: boolean
    // 按钮render数组
    buttonRender?: Array<{
      // 按钮对应的键值
      key: string
      // 按钮render
      render: () => JSX.Element
    }>
    // 是否屏幕移动到报错的位置
    isOpenMoveToErr?: boolean
  }
  const props = withDefaults(defineProps<MyOptions>(), {
    isOpenMoveToErr: true
  })
  const emit = defineEmits(['formItemChange', 'update:form'])
  const { form, validateErrorFunc, validatePassFunc, isOpenMoveToErr } =
    toRefs(props)

  const formValue: Ref<any> = ref(cloneDeep(form?.value || {}))
  // 同步默认值
  emit('update:form', formValue.value)

  /**
   * @description: 抛出的验证函数
   * @param {function} func 执行的函数
   * @return {*}
   */
  const validateByYourSelf = (func): boolean => {
    let result = false
    proxy?.$refs.formRef.validate((errors) => {
      if (errors && isOpenMoveToErr) moveToErr(errors[0][0]?.field)
      result = func(errors)
    })
    return result
  }

  /**
   * @description: 验证某一个规则
   * @param {function} func 执行的函数
   * @param {function} ruleFunc 判断rule
   * @return {*}
   */
  const validateByOneRule = (func, ruleFunc) => {
    proxy?.$refs.formRef.validate(func, ruleFunc)
  }

  /**
   * @description: 处理表单提交
   * @return {*}
   */
  const handleSubmit = async (): Promise<boolean> => {
    let isPass = false
    await proxy?.$refs.formRef.validate((errors) => {
      isPass = !errors

      if (!errors) {
        if (validatePassFunc?.value) validatePassFunc.value()
      } else {
        console.log('errors', errors)
        if (isOpenMoveToErr) moveToErr(errors[0][0]?.field)

        if (validateErrorFunc?.value) validateErrorFunc.value()
      }
    })
    return isPass
  }
  /**
   * @description: 重置表单
   * @param {*} void
   * @return {*}
   */
  const handleReset = (): void => {
    Object.keys(formValue.value).forEach(
      (item) => (formValue.value[item] = null)
    )
  }

  /**
   * @description: 处理表单数据变化
   * @param {string} value
   * @param {FormItemProps} item
   * @return {*}
   */
  const handleChange = (value: string, item: FormItemProps): void => {
    emit('formItemChange', value, item)
  }
  // 自动定位到表单报错项
  const moveToErr = (refName: string): void => {
    nextTick(() => {
      const errorRef = proxy?.$refs[refName]
      if (errorRef.length) {
        const dom = errorRef[0].$el
        // 滚动到屏幕中间
        dom.scrollIntoView({
          block: 'center',
          behavior: 'smooth'
        })
        // 这个当滑动到报错项之后自动获取输入框的焦点，方便用户直接进行输入，延迟 500ms
        setTimeout(() => {
          if (dom.querySelector('input')) {
            dom.querySelector('input').focus()
          }
        }, 500)
      }
    })
  }
  defineExpose({
    validateByYourSelf,
    validateByOneRule,
    handleSubmit,
    handleReset,
    moveToErr
  })

  // 同步更新form对象
  watch(
    formValue,
    (val) => {
      if (val !== form?.value) emit('update:form', val)
    },
    {
      deep: true
    }
  )
  // // form监听实现双向绑定
  if (form) {
    watch(
      form,
      (val) => {
        if (val !== formValue.value) {
          formValue.value = val
        }
      },
      {
        deep: true
      }
    )
  }
</script>

<style lang="less" scoped>
  .pro-form {
    width: 100%;
  }
  .light-green {
    height: 108px;
    background-color: rgba(0, 128, 0, 0.12);
  }
  .green {
    height: 108px;
    background-color: rgba(0, 128, 0, 0.24);
  }
  .buttons {
    display: flex;
    justify-content: center;
  }
  .button {
    margin: 5px;
  }
</style>
